package ar.edu.itba.pod.legajo46470.cluster.node;

import static com.google.common.base.Preconditions.checkNotNull;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.balance.api.AgentsTransfer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.balance.api.NotCoordinatorException;
import ar.edu.itba.event.RemoteEventDispatcher;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.node.api.StatisticReports;
import ar.edu.itba.pod.agent.runner.Agent;
import ar.edu.itba.pod.agent.runner.Simulation;
import ar.edu.itba.pod.legajo46470.balance.AgentsBalancerImpl;
import ar.edu.itba.pod.legajo46470.communication.ClusterCommunication;
import ar.edu.itba.pod.multithread.EventDispatcher;

public class ClusterNode {
	private final static Logger LOGGER = Logger.getLogger(ClusterNode.class);
	private Simulation distributedSimulation;
	private ClusterAdministration clusterAdministration;
	private RemoteEventDispatcher remoteEventDispatcher;
	private EventDispatcher localEventDispatcher;
	private AgentsBalancer agentsBalancer;
	private AgentsTransfer agentsTransfer;
	private NodeInformation nodeInfo;
	private StatisticReports statisticsReports;

	public ClusterNode(ClusterAdministration clusterAdministration,
			NodeInformation nodeInfo) {
		checkNotNull(clusterAdministration,
				"Cluster Administration cannot be null");
		checkNotNull(nodeInfo, "Node Information cannot be null");
		this.clusterAdministration = clusterAdministration;
		this.nodeInfo = nodeInfo;
	}

	public ClusterAdministration getClusterAdministration() {
		return clusterAdministration;
	}

	public RemoteEventDispatcher getRemoteEventDispatcher() {
		return remoteEventDispatcher;
	}

	public EventDispatcher getLocalEventDispatcher() {
		return localEventDispatcher;
	}

	public Simulation getSimulation() {
		return this.distributedSimulation;
	}

	public NodeInformation getNodeInfo() {
		return nodeInfo;
	}

	public AgentsBalancer getAgentsBalancer() {
		return this.agentsBalancer;
	}

	public AgentsTransfer getAgentsTransfer() {
		return agentsTransfer;
	}

	public NodeInformation getCoordinator() {
		AgentsBalancerImpl agentBalancerAux = (AgentsBalancerImpl) agentsBalancer;
		return agentBalancerAux.getCoordinator();
	}

	public void setRemoteEventDispatcher(
			RemoteEventDispatcher remoteEventDispatcher) {
		checkNotNull(remoteEventDispatcher, "Node Information cannot be null");
		this.remoteEventDispatcher = remoteEventDispatcher;
	}

	public void setAgentsBalancer(AgentsBalancer agentsBalancer) {
		checkNotNull(agentsBalancer, "Agent balancer cannot be null");
		this.agentsBalancer = agentsBalancer;
	}

	public void setLocalEventDispatcher(EventDispatcher localEventDispatcher) {
		checkNotNull(localEventDispatcher,
				"Local event dispatcher cannot be null");
		this.localEventDispatcher = localEventDispatcher;
	}

	public void setSimulation(Simulation distributedSimulation) {
		checkNotNull(distributedSimulation,
				"Distributed Simulation cannot be null");
		this.distributedSimulation = distributedSimulation;
	}

	public void setAgentsTransfer(AgentsTransfer agentsTransfer) {
		checkNotNull(agentsTransfer, "Agents Transfer cannot be null");
		this.agentsTransfer = agentsTransfer;
	}

	public void shutdown() {
		AgentsBalancerImpl agentBalancerAux = (AgentsBalancerImpl) agentsBalancer;
		NodeInformation coordinator = agentBalancerAux.getCoordinator();
		ClusterCommunication<AgentsBalancer> coordinatorComm;
		boolean gracefullShutdown = false;
		
		final List<NodeAgent> nodeAgents = new ArrayList<NodeAgent>();
		
		for (Agent agent : distributedSimulation.getAgentsRunning()) {
			nodeAgents.add(new NodeAgent(nodeInfo, agent));
		}
		
		AgentsBalancer coordinatorBalancer;
		coordinatorComm = new ClusterCommunication<AgentsBalancer> (this, Node.AGENTS_BALANCER);
		
		// remove agents from sim
		for (NodeAgent n : nodeAgents) {
			distributedSimulation.remove(n.agent());
		}
		
		while (!gracefullShutdown) {
			try {
				coordinatorBalancer = (AgentsBalancer)coordinatorComm.getStub(coordinator);
				coordinatorBalancer.shutdown(nodeAgents);
				gracefullShutdown = true;
			} catch (RemoteException e) {
				coordinator = agentBalancerAux.getCoordinator();
			} catch (NotCoordinatorException e) {
				coordinator = e.getNewCoordinator();
			} catch (IllegalStateException e) {
				coordinator = agentBalancerAux.getCoordinator();
			}
		}
	}
	

	public StatisticReports getStatisticsReports() {
		return statisticsReports;
	}
	
	public void setStatisticsReports(StatisticReports statisticsReports) {
		this.statisticsReports= statisticsReports;
	}
	
	public void startElection() {
		// sends election message to self: causing message forward to my neighbours
		LOGGER.info("Node: " + nodeInfo + " Starting election!");
		try {
			getAgentsBalancer().bullyElection(nodeInfo, DateTime.now().getMillis());
		} catch (RemoteException e) {
			// local call, never happens
		}
	}
}
